package com.xiaozhen.modbus.service.impl;

import com.alibaba.fastjson.JSON;
import com.serotonin.modbus4j.ModbusFactory;
import com.serotonin.modbus4j.ModbusMaster;
import com.serotonin.modbus4j.code.DataType;
import com.serotonin.modbus4j.exception.ErrorResponseException;
import com.serotonin.modbus4j.exception.ModbusInitException;
import com.serotonin.modbus4j.exception.ModbusTransportException;
import com.serotonin.modbus4j.ip.IpParameters;
import com.serotonin.modbus4j.locator.BaseLocator;
import com.serotonin.modbus4j.msg.WriteCoilRequest;
import com.serotonin.modbus4j.msg.WriteCoilResponse;
import com.xiaozhen.common.core.constant.Constants;
import com.xiaozhen.common.core.domain.device.Device;
import com.xiaozhen.common.core.domain.driver.ParseModbusAttribute;
import com.xiaozhen.common.core.domain.driver.ParseModbusDriver;
import com.xiaozhen.modbus.service.ModbusService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;

import static com.xiaozhen.common.core.utils.ConvertUtils.value;

/**
 * HISTORY
 *
 * @author zhenweisheng
 * @date 2021/11/15 14:50
 * @description:
 */
@Slf4j
@Service
public class ModbusServiceImpl implements ModbusService {

    static ModbusFactory modbusFactory;

    static {
        modbusFactory = new ModbusFactory();
    }

    private volatile Map<Long, ModbusMaster> masterMap = new HashMap<>(64);

    @Override
    public String read(Device device, ParseModbusDriver driver, ParseModbusAttribute attribute) {
        ModbusMaster modbusMaster = getMaster(device, driver.getHost(), driver.getPort());
        if (modbusMaster == null) {
            return null;
        }
        String result = null;
        try {
            result = readValue(modbusMaster, attribute);
        } catch (Exception e) {
            log.error("读取失败,host:{},port:{}", driver.getHost(), driver.getPort());
            return null;
        }
        return result;
    }

    @Override
    public Boolean write(Device device, ParseModbusDriver driver, ParseModbusAttribute attribute, String value) throws Exception {
        ModbusMaster modbusMaster = getMaster(device, driver.getHost(), driver.getPort());
        if (modbusMaster == null) {
            return false;
        }
        Boolean flag = null;
        try {
            flag = writeValue(modbusMaster, attribute, value);
        } catch (Exception e) {
            log.error("写入失败,host:{},port:{}", driver.getHost(), driver.getPort());
            return false;
        }
        return flag;
    }

    /**
     * 获取 Modbus Master
     *
     * @param device 设备
     * @param host   主机
     * @param port   端口号
     * @return ModbusMaster
     * @throws ModbusInitException ModbusInitException
     */
    public ModbusMaster getMaster(Device device, String host, int port) {
        log.debug("Modbus Tcp Connection Device Info {}", JSON.toJSONString(device));
        ModbusMaster modbusMaster = null;
        try {
            modbusMaster = masterMap.get(device.getDeviceId());
            if (null == modbusMaster) {
                IpParameters params = new IpParameters();
                params.setHost(host);
                params.setPort(port);
                modbusMaster = modbusFactory.createTcpMaster(params, true);
                modbusMaster.init();
                masterMap.put(device.getDeviceId(), modbusMaster);
            }
        } catch (Exception e) {
            modbusMaster = null;
            log.error("设备连接失败,host:{},port:{}", host, port);
        }
        return modbusMaster;
    }

    /**
     * 获取 Value
     *
     * @param modbusMaster ModbusMaster
     * @param attribute    Point Info
     * @return String Value
     * @throws ModbusTransportException ModbusTransportException
     * @throws ErrorResponseException   ErrorResponseException
     */
    public String readValue(ModbusMaster modbusMaster, ParseModbusAttribute attribute) throws ModbusTransportException, ErrorResponseException {
        int slaveId = attribute.getSlaveId();
        int functionCode = attribute.getFunctionCode();
        int offset = attribute.getOffset();
        switch (functionCode) {
            case 1:
                BaseLocator<Boolean> coilLocator = BaseLocator.coilStatus(slaveId, offset);
                Boolean coilValue = modbusMaster.getValue(coilLocator);
                return String.valueOf(coilValue);
            case 2:
                BaseLocator<Boolean> inputLocator = BaseLocator.inputStatus(slaveId, offset);
                Boolean inputStatusValue = modbusMaster.getValue(inputLocator);
                return String.valueOf(inputStatusValue);
            case 3:
                BaseLocator<Number> holdingLocator = BaseLocator.holdingRegister(slaveId, offset, getValueType(attribute.getAttributeType()));
                Number holdingValue = modbusMaster.getValue(holdingLocator);
                return String.valueOf(holdingValue);
            case 4:
                BaseLocator<Number> inputRegister = BaseLocator.inputRegister(slaveId, offset, getValueType(attribute.getAttributeType()));
                Number inputRegisterValue = modbusMaster.getValue(inputRegister);
                return String.valueOf(inputRegisterValue);
            default:
                return "0";
        }
    }

    /**
     * 写 Value
     *
     * @param modbusMaster ModbusMaster
     * @param attribute    attribute
     * @param value        String Value
     * @return Write Result
     * @throws ModbusTransportException ModbusTransportException
     * @throws ErrorResponseException   ErrorResponseException
     */
    public boolean writeValue(ModbusMaster modbusMaster, ParseModbusAttribute attribute, String value) throws ModbusTransportException, ErrorResponseException {
        int slaveId = attribute.getSlaveId();
        int functionCode = attribute.getFunctionCode();
        int offset = attribute.getOffset();
        switch (functionCode) {
            case 1:
                boolean coilValue = value(attribute.getAttributeType(), value);
                WriteCoilRequest coilRequest = new WriteCoilRequest(slaveId, offset, coilValue);
                WriteCoilResponse coilResponse = (WriteCoilResponse) modbusMaster.send(coilRequest);
                return !coilResponse.isException();
            case 3:
                BaseLocator<Number> locator = BaseLocator.holdingRegister(slaveId, offset, getValueType(attribute.getAttributeType()));
                modbusMaster.setValue(locator, value(attribute.getAttributeType(), value));
                return true;
            default:
                return false;
        }
    }

    /**
     * 获取数据类型
     * 说明：此处可根据实际项目情况进行拓展
     * 1.swap 交换
     * 2.大端/小端,默认是大端
     * 3.拓展其他数据类型
     *
     * @param type Value Type
     * @return Modbus Data Type
     */
    public int getValueType(String type) {
        switch (type.toLowerCase()) {
            case Constants.ValueType.LONG:
                return DataType.FOUR_BYTE_INT_SIGNED;
            case Constants.ValueType.FLOAT:
                return DataType.FOUR_BYTE_FLOAT;
            case Constants.ValueType.DOUBLE:
                return DataType.EIGHT_BYTE_FLOAT;
            default:
                return DataType.TWO_BYTE_INT_SIGNED;
        }
    }
}
